
#include <stdexcept>
#include <string.h>

#ifndef PURG_C_INTERFACE_COMPILE
namespace purg
{
#endif

  template <typename GEN1_FCN , typename GEN2_FCN>
  combined_generator_tmpl<GEN1_FCN , GEN2_FCN>
              ::combined_generator_tmpl ( gen1_seed seed1 , gen2_seed seed2 , size_t generator_id )
                        : _gen1_buffer ( GEN1_FCN::buffer_length )
                        , _gen1_offset ( 0 )
                        , _gen2_buffer ( GEN2_FCN::buffer_length )
                        , _gen2_offset ( 0 )
                        , _generator_id ( generator_id )
  {
    GEN1_FCN::initialize ( _gen1_state , _gen1_buffer , seed1 , generator_id );
    GEN2_FCN::initialize ( _gen2_state , _gen2_buffer , seed2 , generator_id );
  }

  template <typename GEN1_FCN , typename GEN2_FCN>
  void  combined_generator_tmpl<GEN1_FCN , GEN2_FCN>
              ::get_raw_buffer ( size_t inLength , void *in_buffer )
  {
    size_t length = inLength;
    size_t accum = 0;
    while ( length > 0 )
    {
      size_t amt = _gen1_buffer.copyTo ( in_buffer , accum , _gen1_offset , length , GEN1_FCN::buffer_length );
      length -= amt;
      _gen1_offset += amt;
      accum += amt;
      if ( _gen1_offset >= GEN1_FCN::buffer_length )
      {
        GEN1_FCN::update_buffer ( _gen1_state , _gen1_buffer );
        _gen1_offset = 0;
      }
    }

    length = inLength;
    accum = 0;
    while ( length > 0 )
    {
      size_t amt = _gen2_buffer.xorTo ( in_buffer , accum , _gen2_offset , length , GEN2_FCN::buffer_length );
      length -= amt;
      _gen2_offset += amt;
      accum += amt;
      if ( _gen2_offset >= GEN2_FCN::buffer_length )
      {
        GEN2_FCN::update_buffer ( _gen2_state , _gen2_buffer );
        _gen2_offset = 0;
      }
    }
  }

  template <typename GEN1_FCN , typename GEN2_FCN>
  void  combined_generator_tmpl<GEN1_FCN , GEN2_FCN>
              :: unlock ()
  {
    GEN1_FCN::unlock ( _gen1_state );
    GEN2_FCN::unlock ( _gen2_state );
  }

  template <typename GEN1_FCN , typename GEN2_FCN>
  void  combined_generator_tmpl<GEN1_FCN , GEN2_FCN>
              :: lock ()
  {
    GEN1_FCN::lock ( _gen1_state );
    GEN2_FCN::lock ( _gen2_state );
  }


  template <typename GEN1_FCN , typename GEN2_FCN>
  template <typename POD_TYPE>
  void  combined_generator_tmpl<GEN1_FCN , GEN2_FCN>
              ::get ( POD_TYPE &in_buffer )
  {
    lock ();
    get_raw_buffer ( sizeof ( POD_TYPE ) , static_cast<void *> ( &in_buffer ) );
    unlock ();
  }

  template <typename GEN1_FCN , typename GEN2_FCN>
  void  combined_generator_tmpl<GEN1_FCN , GEN2_FCN>
              ::get ( double & )
  {
    throw std::runtime_error ( "Cannot get a uniformly distributed double over all doubles" );
  }

  template <typename GEN1_FCN , typename GEN2_FCN>
  void  combined_generator_tmpl<GEN1_FCN , GEN2_FCN>
              ::get ( float & )
  {
    throw std::runtime_error ( "Cannot get a uniformly distributed float over all floats" );
  }

  template <typename GEN1_FCN , typename GEN2_FCN>
  void  combined_generator_tmpl<GEN1_FCN , GEN2_FCN>
              ::get ( double &in_buffer , double minimum , double maximum )
  {
    lock();
    unsigned long  tbuf;
    long minus_one = -1;
    get_raw_buffer ( sizeof ( unsigned long ) , static_cast<void *> ( &tbuf ) );
    in_buffer = static_cast<double> ( tbuf ) /
             static_cast<double> ( 
                      static_cast<unsigned long> ( minus_one ) );
    in_buffer *= maximum - minimum;
    in_buffer += minimum;
    unlock();
  }

  template <typename GEN1_FCN , typename GEN2_FCN>
  void  combined_generator_tmpl<GEN1_FCN , GEN2_FCN>
              ::get ( float &in_buffer , float minimum , float maximum )
  {
    lock();
    unsigned long  tbuf;
    long minus_one = -1;
    get_raw_buffer ( sizeof ( unsigned long ) , static_cast<void *> ( &tbuf ) );
    in_buffer = static_cast<float> ( tbuf ) /
             static_cast<float> ( 
                      static_cast<unsigned long> ( minus_one ) );
    in_buffer *= maximum - minimum;
    in_buffer += minimum;
    unlock();
  }

  template <typename GEN1_FCN , typename GEN2_FCN>
  template <typename POD_TYPE>
  void  combined_generator_tmpl<GEN1_FCN , GEN2_FCN>
              ::get ( POD_TYPE &in_buffer , POD_TYPE minimum , POD_TYPE maximum )
  {
    lock();
    get_raw_buffer ( sizeof ( POD_TYPE ) , static_cast<void *> ( &in_buffer ) );
    if ( maximum != minimum )
    {
      if ( in_buffer < 0 ) in_buffer *= -1;
      in_buffer %= ( maximum - minimum );
      in_buffer += minimum;
    }
    unlock();
  }

  template <typename GEN1_FCN , typename GEN2_FCN>
  typename combined_generator_tmpl<GEN1_FCN , GEN2_FCN>::gen1_state & combined_generator_tmpl<GEN1_FCN , GEN2_FCN>::get_gen1_state ()
  {
    return _gen1_state;
  }

  template <typename GEN1_FCN , typename GEN2_FCN>
  typename combined_generator_tmpl<GEN1_FCN , GEN2_FCN>::gen2_state & combined_generator_tmpl<GEN1_FCN , GEN2_FCN>::get_gen2_state ()
  {
    return _gen2_state;
  }

#ifndef PURG_C_INTERFACE_COMPILE
}
#endif

